using System;
using System.Collections;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using CommunicationShared;
using PDUConverter;

namespace Communication
{
    /// <summary>
    /// Interacts with a mobile phone to execute various functions.
    /// </summary>
    public class GsmCommMain
    {
        /// <summary>
        /// The default baud rate to use.
        /// </summary>
        public const int DefaultBaudRate = 0x4b00;
        /// <summary>
        /// The default port to connect to.
        /// </summary>
        public const int DefaultPortNumber = 1;
        /// <summary>
        /// The default communication timeout.
        /// </summary>
        public const int DefaultTimeout = 300;
        private readonly GsmPhone _theDevice;
        private static bool VersionInfoLogged;

        /// <summary>
        /// The event that occurs when a new line was added to the log.
        /// </summary>
        public event LoglineAddedEventHandler LoglineAdded;

        /// <summary>
        /// The event that occurs when a new message was received.
        /// </summary>
        public event MessageReceivedEventHandler MessageReceived;

        /// <summary>
        /// The event that occurs after a successful message transfer.
        /// </summary>
        public event MessageEventHandler MessageSendComplete;

        /// <summary>
        /// The event that occurs after a failed message transfer.
        /// </summary>
        public event MessageErrorEventHandler MessageSendFailed;

        /// <summary>
        /// The event that occurs immediately before transferring a new message.
        /// </summary>
        public event MessageEventHandler MessageSendStarting;

        /// <summary>The event that occurs when the phone is connected.</summary>
        public event EventHandler PhoneConnected;

        /// <summary>The event that occurs when the phone is disconnected.</summary>
        public event EventHandler PhoneDisconnected;

        /// <summary>The event that occurs when receiving from the phone is completed.</summary>
        /// <remarks>This event is only fired by reading operations that may take longer to complete.</remarks>
        public event ProgressEventHandler ReceiveComplete;

        /// <summary>The event that occurs when new data was received from the phone.</summary>
        /// <remarks>This event is only fired by reading operations that may take longer to complete.</remarks>
        public event ProgressEventHandler ReceiveProgress;

        /// <summary>
        /// Initializes a new instance of the class using default parameters.
        /// </summary>
        /// <remarks>Uses the default values: port=COM1, baud rate=19200, timeout=300ms.</remarks>
        public GsmCommMain()
        {
            _theDevice = new GsmPhone(1, 0x4b00, 300);
        }

        /// <summary>
        /// Initializes a new instance of the class using the specified parameters.
        /// </summary>
        /// <param name="portNum">The communication (COM) port to use.</param>
        /// <remarks>Uses the default values: baud rate=19200, timeout=300ms.</remarks>
        public GsmCommMain(int portNum)
        {
            _theDevice = new GsmPhone(portNum, 0x4b00, 300);
        }

        /// <summary>
        /// Initializes a new instance of the class using the specified parameters.
        /// </summary>
        /// <param name="portNum">The communication (COM) port to use.</param>
        /// <param name="baudRate">The baud rate (speed) to use.</param>
        /// <remarks>Uses the default values: timeout=300ms.</remarks>
        public GsmCommMain(int portNum, int baudRate)
        {
            _theDevice = new GsmPhone(portNum, baudRate, 300);
        }

        /// <summary>
        /// Initializes a new instance of the class using the specified parameters.
        /// </summary>
        /// <param name="portNum">The communication (COM) port to use.</param>
        /// <param name="baudRate">The baud rate (speed) to use.</param>
        /// <param name="timeout">The communication timeout in milliseconds.</param>
        public GsmCommMain(int portNum, int baudRate, int timeout)
        {
            _theDevice = new GsmPhone(portNum, baudRate, timeout);
        }

        /// <summary>
        /// Acknowledges a new received short message that was directly routed to the application.
        /// </summary>
        /// <remarks>Acknowledges are required for most received messages if GsmCommMain.IsAcknowledgeRequired
        /// returns true. The acknowledge requirement can be changed with the GsmCommMain.RequireAcknowledge(System.Boolean)
        /// method.
        /// </remarks>
        public void AcknowledgeNewMessage()
        {
            _theDevice.AcknowledgeNewMessage();
        }

        private void AsyncCallback(IAsyncResult ar)
        {
            var result = (AsyncResult) ar;
            if (result.AsyncDelegate is MessageEventHandler)
            {
                LogIt("Ending async MessageEventHandler call");
                ((MessageEventHandler) result.AsyncDelegate).EndInvoke(ar);
            }
            else if (result.AsyncDelegate is MessageErrorEventHandler)
            {
                LogIt("Ending async MessageErrorEventHandler call");
                ((MessageErrorEventHandler) result.AsyncDelegate).EndInvoke(ar);
            }
            else
            {
                LogIt("Warning: AsyncCallback got unknown delegate: " + result.AsyncDelegate.GetType());
            }
        }

        /// <summary>
        /// Closes the connection to the device.
        /// </summary>
        /// <remarks>You can check the current connection state with the IsOpen method.
        /// </remarks>
        public void Close()
        {
            _theDevice.Close();
            DisconnectEvents();
        }

        private void ConnectEvents()
        {
            _theDevice.LoglineAdded += TheDeviceLoglineAdded;
            _theDevice.ReceiveProgress += TheDeviceReceiveProgress;
            _theDevice.ReceiveComplete += TheDeviceReceiveComplete;
            _theDevice.MessageReceived += TheDeviceMessageReceived;
            _theDevice.PhoneConnected += TheDevicePhoneConnected;
            _theDevice.PhoneDisconnected += TheDevicePhoneDisconnected;
        }

        /// <summary>
        /// Creates a new phonebook entry.
        /// </summary>
        /// <param name="entry">The entry to create.</param>
        /// <param name="storage">The storage to save the entry.</param>
        /// <remarks>The PhonebookEntry.Index property of the entry is ignored, 
        /// the entry is always saved in the first free location. All other properties must be set
        /// correctly.
        public void CreatePhonebookEntry(PhonebookEntry entry, string storage)
        {
            _theDevice.SelectPhonebookStorage(storage);
            _theDevice.WritePhonebookEntry(entry);
        }

        /// <summary>
        /// Decodes a received short message.
        /// </summary>
        /// <param name="message">The message to decode</param>
        /// <returns>The decoded message as PduConverter.SmsPdu object.</returns>
        public SmsPdu DecodeReceivedMessage(ShortMessage message)
        {
            IncomingSmsPdu pdu;
            try
            {
                pdu = IncomingSmsPdu.Decode(message.Data, true, message.Length);
            }
            catch (Exception exception)
            {
                LogIt("Error: IncomingSmsPdu decoder can't decode message: " + exception.Message);
                LogIt("  Message data = \"" + message.Data + "\"");
                LogIt("  Message length = " + message.Length);
                throw;
            }
            return pdu;
        }

        /// <summary>
        /// Decodes a short message read from the phone.
        /// </summary>
        /// <param name="message">The message to decode.</param>
        /// <returns>The decoded short message.</returns>
        /// <remarks>
        /// Use this function to decode messages that were read with the ReadRawMessages(GsmComm.GsmCommunication.PhoneMessageStatus,System.String) function.
        /// </remarks>
        /// There is no decoder available that can handle the message's status.</exception>
        public SmsPdu DecodeShortMessage(ShortMessageFromPhone message)
        {
            var status = (PhoneMessageStatus) message.Status;
            switch (status)
            {
                case PhoneMessageStatus.ReceivedUnread:
                case PhoneMessageStatus.ReceivedRead:
                    try
                    {
                        return DecodeReceivedMessage(message);
                    }
                    catch (Exception)
                    {
                        LogIt("Unable to decode message with specified status - trying other variants.");
                        return DecodeStoredMessage(message);
                    }                    
                case PhoneMessageStatus.StoredUnsent:
                case PhoneMessageStatus.StoredSent:
                    break;

                default:
                    {
                        var str = "No decoder available for message of status \"" + status + "\" (index " + message.Index+ ").";
                        LogIt("Error: " + str);
                        throw new CommException(str);
                    }
            }
            try
            {
                return DecodeStoredMessage(message);
            }
            catch (Exception)
            {
                LogIt("Unable to decode message with specified status - trying other variants.");
                return DecodeReceivedMessage(message);
            }
        }

        private SmsPdu DecodeStoredMessage(ShortMessage message)
        {
            OutgoingSmsPdu pdu;
            try
            {
                pdu = OutgoingSmsPdu.Decode(message.Data, true, message.Length);
            }
            catch (Exception exception)
            {
                LogIt("Error: OutgoingSmsPdu decoder can't decode message: " + exception.Message);
                LogIt("  Message data = \"" + message.Data + "\"");
                LogIt("  Message length = " + message.Length);
                throw;
            }
            return pdu;
        }

        /// <summary>
        /// Deletes all phonebook entries.
        /// </summary>
        /// <param name="storage">The storage to use.</param>
        /// <remarks>To delete single entries, use the DeletePhonebookEntry(System.Int32,System.String) function.
        /// </remarks>
        public void DeleteAllPhonebookEntries(string storage)
        {
            _theDevice.SelectPhonebookStorage(storage);
            foreach (PhonebookEntry entry in _theDevice.ReadPhonebookEntries())
                _theDevice.DeletePhonebookEntry(entry.Index);
        }

        /// <summary>
        /// Deletes the specified short message.
        /// </summary>
        /// <param name="index">The index of the message to delete.</param>
        /// <param name="storage">The storage to use.</param>
        /// <remarks>
        /// To delete a group of messages, use the DeleteMessages(GsmComm.GsmCommunication.DeleteScope,System.String) function.
        /// </remarks>
        public void DeleteMessage(int index, string storage)
        {
            LogIt("Deleting message...");
            _theDevice.SelectReadStorage(storage);
            _theDevice.DeleteMessage(index);
        }

        /// <summary>
        /// Deletes the specified group of messages.
        /// </summary>
        /// <param name="scope">Specifies the messages that are affected by this command.</param>
        /// <param name="storage">The storage to use.</param>
        /// <remarks>
        /// To delete a single message, use the DeleteMessage(System.Int32,System.String) function.
        /// </remarks>
        public void DeleteMessages(DeleteScope scope, string storage)
        {
            var num = (int) scope;
            var delflag = (DeleteFlag) Enum.Parse(typeof(DeleteFlag), num.ToString());
            LogIt("Deleting \"" + scope + "\" messages in storage \"" + storage + "\"...");
            _theDevice.SelectReadStorage(storage);
            _theDevice.DeleteMessage(1, delflag);
        }

        /// <summary>
        /// Deletes a phonebook entry.
        /// </summary>
        /// <param name="index">The index of the entry to delete.</param>
        /// <param name="storage">The storage to use.</param>
        /// <remarks>To delete all phonebook entries at once, use the DeleteAllPhonebookEntries(System.String) function.
        /// </remarks>
        public void DeletePhonebookEntry(int index, string storage)
        {
            _theDevice.SelectPhonebookStorage(storage);
            _theDevice.DeletePhonebookEntry(index);
        }

        /// <summary>
        /// Disables all message notifications.
        /// </summary>
        /// <remarks>
        /// <para>Call this function after a call to EnableMessageNotifications to disable this functionality
        /// again.</para>
        /// <para>It's highly recommended to disable notifications again before closing the connection to
        /// the phone. If it doesn't get disabled, the phone will still try to send notifications, which will not be
        /// successful. The phone <b>may</b> buffer unsuccessful notifications, but you should generally not rely on this.</para>
        /// </remarks>
        public void DisableMessageNotifications()
        {
            _theDevice.SetMessageIndications(new MessageIndicationSettings(0, 0, 0, 0, 0));
        }

        /// <summary>
        /// Disables all message routings.
        /// </summary>
        /// <remarks>
        /// <para>Call this function after a call to EnableMessageRouting" to disable this functionality
        /// again.</para>
        /// <para><b>CAUTION:</b> It's highly recommended to disable routing again before closing the connection to
        /// the phone. If it doesn't get disabled, the phone will still try to route messages on, which will not be
        /// successful. You <b>may</b> lose messages in such a case if the phone doesn't buffer unsuccessful routings.
        /// </para>
        /// </remarks>
        public void DisableMessageRouting()
        {
            _theDevice.SetMessageIndications(new MessageIndicationSettings(0, 0, 0, 0, 0));
        }

        /// <summary>
        /// Disables the SMS batch mode.
        /// </summary>
        /// <remarks>Disables the SMS batch mode previously enabled with EnablePermanentSmsBatchMode or EnableTemporarySmsBatchMode.
        /// </remarks>
        public void DisableSmsBatchMode()
        {
            _theDevice.SetMoreMessagesToSend(MoreMessagesMode.Disabled);
        }

        private void DisconnectEvents()
        {
            _theDevice.LoglineAdded -= TheDeviceLoglineAdded;
            _theDevice.ReceiveProgress -= TheDeviceReceiveProgress;
            _theDevice.ReceiveComplete -= TheDeviceReceiveComplete;
            _theDevice.MessageReceived -= TheDeviceMessageReceived;
            _theDevice.PhoneConnected -= TheDevicePhoneConnected;
            _theDevice.PhoneDisconnected -= TheDevicePhoneDisconnected;
        }

        /// <summary>
        /// Enables notifications of new received short messages.
        /// </summary>
        /// <remarks>
        /// <para>When a new message is received that is either a standard SMS message or a status report,
        /// the MessageReceived event is fired. The GsmCommunication.IMessageIndicationObject
        /// in this event must be cast to a GsmCommunication.MemoryLocation object, which contains the memory
        /// location of the message that was saved in the phone. You can then use the ReadMessage(System.Int32,System.String)
        /// method (for example) to read the new message.</para>
        /// <para>The supported notification settings vary between different phone models. Therefore the phone is
        /// queried first and then the supported settings of the phone are compared to the settings needed for
        /// the notification functionality to work. If a specific setting is not supported and there is no
        /// alternative setting possible, an exception will be raised.</para>
        /// <para>To disable message notifications, use the DisableMessageNotifications function.
        /// </para>
        /// <para>EnableMessageNotifications can't be used with EnableMessageRouting at the same time.
        /// Disable one functionality before using the other.</para>
        /// </remarks>
        public void EnableMessageNotifications()
        {
            var supportedIndications = _theDevice.GetSupportedIndications();
            var bufferAndFlush = MessageIndicationMode.BufferAndFlush;
            if (!supportedIndications.SupportsMode(bufferAndFlush))
            {
                if (!supportedIndications.SupportsMode(MessageIndicationMode.SkipWhenReserved))
                {
                    if (!supportedIndications.SupportsMode(MessageIndicationMode.ForwardAlways))
                        throw new CommException("The phone does not support any of the required message indication modes.");
                    bufferAndFlush = MessageIndicationMode.ForwardAlways;
                }
                else
                    bufferAndFlush = MessageIndicationMode.SkipWhenReserved;
            }
            const SmsDeliverIndicationStyle routeMemoryLocation = SmsDeliverIndicationStyle.RouteMemoryLocation;
            if (!supportedIndications.SupportsDeliverStyle(routeMemoryLocation))
            {
                throw new CommException("The phone does not support notification for standard SMS (SMS-DELIVER) messages. ");
            }
            const CbmIndicationStyle disabled = CbmIndicationStyle.Disabled;
            var style = SmsStatusReportIndicationStyle.RouteMemoryLocation;
            if (!supportedIndications.SupportsStatusReportStyle(style))
            {
                LogIt("Attention: The phone does not support notification about new status reports. As a fallback it will be disabled.");
                style = SmsStatusReportIndicationStyle.Disabled;
            }
            var flush = IndicationBufferSetting.Flush;
            if (!supportedIndications.SupportsBufferSetting(flush))
            {
                if (!supportedIndications.SupportsBufferSetting(IndicationBufferSetting.Clear))
                    throw new CommException("The phone does not support any of the required buffer settings.");
                flush = IndicationBufferSetting.Clear;
            }
            var settings = new MessageIndicationSettings(bufferAndFlush, routeMemoryLocation, disabled, style, flush);
            _theDevice.SetMessageIndications(settings);
        }

        /// <summary>
        /// Enables direct routing of new received short messages to the application.
        /// </summary>
        /// <remarks>
        /// <para>When a new message is received that is either a standard SMS message or a status report,
        /// the MessageReceived event is fired. The GsmCommunication.IMessageIndicationObject
        /// in this event must be cast to a GsmCommunication.ShortMessage object which can then be decoded using
        /// DecodeReceivedMessage(GsmComm.GsmCommunication.ShortMessage).</para>
        /// <para><b>CAUTION:</b> Because the messages are forwared directly, they are <b>not</b> saved in the phone.
        /// If for some reason the message must be saved it must explicitly be done afterwards. Either by using
        /// the WriteRawMessage(GsmComm.GsmCommunication.ShortMessage,System.String,System.Int32) or WriteRawMessageWithoutStatus(GsmComm.GsmCommunication.ShortMessage,System.String) functions
        /// to write the message back to the phone or by storing the message somewhere else for later use.</para>
        /// <para>It may be necessary to acknlowledge new routed messages to the phone, either because this
        /// is desired for reliable message transfer or because it is preconfigured in the phone. Use 
        /// IsAcknowledgeRequired to find out if acknowledgements must be done. To do the actual
        /// acknowledge, use AcknowledgeNewMessage.</para>
        /// <para>The supported routing settings vary between different phone models. Therefore the phone is
        /// queried first and then the supported settings of the phone are compared to the settings needed for
        /// the routing functionality to work. If a specific setting is not supported and there is no
        /// alternative setting possible, an exception will be raised.</para>
        /// <para>To disable message routing, use the DisableMessageRouting function.</para>
        /// <para>EnableMessageRouting can't be used with EnableMessageNotifications at the same time.
        /// Disable one functionality before using the other.</para>
        /// </remarks>
        public void EnableMessageRouting()
        {
            var supportedIndications = _theDevice.GetSupportedIndications();
            var bufferAndFlush = MessageIndicationMode.BufferAndFlush;
            if (!supportedIndications.SupportsMode(bufferAndFlush))
            {
                if (!supportedIndications.SupportsMode(MessageIndicationMode.SkipWhenReserved))
                {
                    if (!supportedIndications.SupportsMode(MessageIndicationMode.ForwardAlways))
                        throw new CommException("The phone does not support any of the required message indication modes.");
                    bufferAndFlush = MessageIndicationMode.ForwardAlways;
                }
                else
                {
                    bufferAndFlush = MessageIndicationMode.SkipWhenReserved;
                }
            }
            const SmsDeliverIndicationStyle routeMessage = SmsDeliverIndicationStyle.RouteMessage;
            if (!supportedIndications.SupportsDeliverStyle(routeMessage))
            {
                throw new CommException("The phone does not support routing of standard SMS (SMS-DELIVER) messages.");
            }
            const CbmIndicationStyle disabled = CbmIndicationStyle.Disabled;
            var style = SmsStatusReportIndicationStyle.RouteMessage;
            if (!supportedIndications.SupportsStatusReportStyle(style))
            {
                LogIt("Attention: The phone does not support routing of new status reports. As a fallback it will be disabled.");
                style = SmsStatusReportIndicationStyle.Disabled;
            }
            var flush = IndicationBufferSetting.Flush;
            if (!supportedIndications.SupportsBufferSetting(flush))
            {
                if (!supportedIndications.SupportsBufferSetting(IndicationBufferSetting.Clear))
                    throw new CommException("The phone does not support any of the required buffer settings.");
                flush = IndicationBufferSetting.Clear;
            }
            var settings = new MessageIndicationSettings(bufferAndFlush, routeMessage, disabled, style, flush);
            _theDevice.SetMessageIndications(settings);
        }

        /// <summary>
        /// Enables the SMS batch mode permanently.
        /// </summary>
        /// <remarks>
        /// <para>When this feature is enabled (and supported by the network), multiple messages can be sent much
        /// faster, as the SMS link is kept open between the messages.</para>
        /// <para>If there is no message sent for 1-5 seconds (the exact value is up to the phones's implementation)
        /// after the last sent SMS message, the SMS link is closed but the batch mode is kept enabled. You have
        /// to explicitely disable it with DisableSmsBatchMode.</para>
        /// <para>If you don't want to care about turning the batch mode off when done with sending,
        /// consider using EnableTemporarySmsBatchMode instead.</para>
        /// </remarks>
        public void EnablePermanentSmsBatchMode()
        {
            _theDevice.SetMoreMessagesToSend(MoreMessagesMode.Permanent);
        }

        /// <summary>
        /// Enables the SMS batch mode temporarily.
        /// </summary>
        /// <remarks>
        /// <para>When this feature is enabled (and supported by the network), multiple messages can be sent much
        /// faster, as the SMS link is kept open between the messages.</para>
        /// <para>If there is no message sent for 1-5 seconds (the exact value is up to the phones's implementation)
        /// after the last sent SMS message, the SMS link is closed and the batch mode is disabled
        /// automatically. You have to re-enable it before you send the next batch of messages, but you
        /// also don't have to care about turning it off when finished with sending.</para>
        /// </remarks>
        public void EnableTemporarySmsBatchMode()
        {
            _theDevice.SetMoreMessagesToSend(MoreMessagesMode.Temporary);
        }

        /// <summary>
        /// Enters a password at the phone which is necessary before it can operated.
        /// </summary>
        /// <param name="pin">The SIM PIN, SIM PUK or other password required.</param>
        /// <remarks>Get the current PIN status with GetPinStatus to check whether a password must be entered.</remarks>
        public void EnterPin(string pin)
        {
            _theDevice.EnterPin(pin);
        }

        /// <summary>
        /// Finds phonebook entries.
        /// </summary>
        /// <param name="findtext">The text in the entry to find.</param>
        /// <param name="storage">The storage to search.</param>
        /// <returns>An array of phonebook entries matching the specified criteria.</returns>
        /// <remarks>The device executes the actual search. If you need the storage information with the results,
        /// use the FindPhonebookEntriesWithStorage(System.String,System.String) function instead.
        /// </remarks>
        public PhonebookEntry[] FindPhonebookEntries(string findtext, string storage)
        {
            new ArrayList();
            _theDevice.SelectPhonebookStorage(storage);
            return _theDevice.FindPhonebookEntries(findtext);
        }

        /// <summary>
        /// Finds phonebook entries and saves the storage where they came from.
        /// </summary>
        /// <param name="findtext">The text in the entry to find.</param>
        /// <param name="storage">The storage to search.</param>
        /// <returns>An array of phonebook entries matching the specified criteria.</returns>
        /// <remarks>The device executes the actual search. If you don't need the storage information with the
        /// results, use the FindPhonebookEntries(System.String,System.String) function instead.
        /// </remarks>
        public PhonebookEntryWithStorage[] FindPhonebookEntriesWithStorage(string findtext, string storage)
        {
            var list = new ArrayList();
            _theDevice.SelectPhonebookStorage(storage);
            foreach (PhonebookEntry entry in _theDevice.FindPhonebookEntries(findtext))
                list.Add(new PhonebookEntryWithStorage(entry, storage));
            var array = new PhonebookEntryWithStorage[list.Count];
            list.CopyTo(array, 0);
            return array;
        }

        /// <summary>
        /// Gets the phone's battery charging status.
        /// </summary>
        /// <returns>A GsmCommunication.BatteryChargeInfo object containing the battery details.</returns>
        public BatteryChargeInfo GetBatteryCharge()
        {
            return _theDevice.GetBatteryCharge();
        }

        /// <summary>
        /// Gets the currenty selected text mode character set.
        /// </summary>
        /// <returns>A string containing the name of the currently selected character set.</returns>
        /// <remarks>
        /// </remarks>
        public string GetCurrentCharacterSet()
        {
            return _theDevice.GetCurrentCharacterSet();
        }

        /// <summary>
        /// AT+COPS. Gets the currently selected network operator.
        /// </summary>
        /// <returns>An GsmCommunication.OperatorInfo object containing the data or null if there is no current operator.</returns>
        public OperatorInfo GetCurrentOperator()
        {
            return _theDevice.GetCurrentOperator();
        }

        /// <summary>
        /// Gets the memory status of the specified message storage.
        /// </summary>
        /// <param name="storage">The storage to return the status for</param>
        /// <returns>An object containing the memory status of the specified storage.</returns>
        public MemoryStatus GetMessageMemoryStatus(string storage)
        {
            return _theDevice.SelectReadStorage(storage);
        }

        /// <summary>
        /// Gets the device's supported message storages.
        /// </summary>
        /// <returns>A GsmCommunication.MessageStorageInfo object that contains details about the supported storages.</returns>
        public MessageStorageInfo GetMessageStorages()
        {
            return _theDevice.GetMessageStorages();
        }

        /// <summary>
        /// Determines the current mode to select a network operator.
        /// </summary>
        /// <returns>The current mode.</returns>
        public OperatorSelectionMode GetOperatorSelectionMode()
        {
            var operatorSelectionMode = _theDevice.GetOperatorSelectionMode();
            if (!Enum.IsDefined(typeof(OperatorSelectionMode), operatorSelectionMode))
                throw new CommException("Unknown operator selection mode " + operatorSelectionMode);
            return (OperatorSelectionMode) Enum.Parse(typeof(OperatorSelectionMode), operatorSelectionMode.ToString());
        }

        /// <summary>
        /// Gets the entire phonebook of the selected storage.
        /// </summary>
        /// <param name="storage">The storage to read the data from.</param>
        /// <returns>An array of phonebook entries. If you need the storage information with the
        /// results, use the GetPhonebookWithStorage(System.String) function instead.</returns>
        /// <remarks>
        /// </remarks>
        public PhonebookEntry[] GetPhonebook(string storage)
        {
            new ArrayList();
            _theDevice.SelectPhonebookStorage(storage);
            return _theDevice.ReadPhonebookEntries();
        }

        /// <summary>
        /// Gets the memory status of the specified phonebook storage.
        /// </summary>
        /// <param name="storage">The storage to return the status for</param>
        /// <returns>An object containing the memory status of the specified storage.</returns>
        public MemoryStatusWithStorage GetPhonebookMemoryStatus(string storage)
        {
            _theDevice.SelectPhonebookStorage(storage);
            return _theDevice.GetPhonebookMemoryStatus();
        }

        /// <summary>
        /// Gets the device's supported phonebook storages.
        /// </summary>
        /// <returns>An array of supported storages in coded form, usually "SM" for SIM, "ME" for
        /// phone, etc.</returns>
        public string[] GetPhonebookStorages()
        {
            return _theDevice.GetPhonebookStorages();
        }

        /// <summary>
        /// Gets the entire phonebook of the selected storage and saves the storage where the entries came from.
        /// </summary>
        /// <param name="storage">The storage to read the data from.</param>
        /// <returns>An array of phonebook entries. If you don't need the storage information with the
        /// results, use the GetPhonebook(System.String) function instead.</returns>
        /// <remarks>
        /// </remarks>
        public PhonebookEntryWithStorage[] GetPhonebookWithStorage(string storage)
        {
            var list = new ArrayList();
            _theDevice.SelectPhonebookStorage(storage);
            foreach (PhonebookEntry entry in _theDevice.ReadPhonebookEntries())
                list.Add(new PhonebookEntryWithStorage(entry, storage));
            var array = new PhonebookEntryWithStorage[list.Count];
            list.CopyTo(array, 0);
            return array;
        }

        /// <summary>
        /// Returns a value indicating whether some password must be entered at the phone or not.
        /// </summary>
        /// <returns>The current PIN status as one of the GsmCommunication.PinStatus values.</returns>
        public PinStatus GetPinStatus()
        {
            return _theDevice.GetPinStatus();
        }

        /// <summary>
        /// Gets the signal quality as calculated by the phone.
        /// </summary>
        /// <returns>A GsmCommunication.SignalQualityInfo object containing the signal details.</returns>
        public SignalQualityInfo GetSignalQuality()
        {
            return _theDevice.GetSignalQuality();
        }

        /// <summary>
        /// Gets the current SMS batch mode setting.
        /// </summary>
        /// <returns>The current mode.</returns>
        public MoreMessagesMode GetSmsBatchModeSetting()
        {
            return _theDevice.GetMoreMessagesToSend();
        }

        /// <summary>
        /// Gets the SMS Service Center Address.
        /// </summary>
        /// <returns>The current SMSC address</returns>
        /// <remarks>This command returns the SMSC address, through which SMS messages are transmitted.
        /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is
        /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals
        /// zero.</remarks>
        public AddressData GetSmscAddress()
        {
            return _theDevice.GetSmscAddress();
        }

        /// <summary>
        /// Returns the MSISDNs related to the subscriber.
        /// </summary>
        /// <returns>An array of SubscriberInfo objects with one for each MSISDN
        /// (Mobile Subscriber ISDN Number), depending on the services subscribed.</returns>
        /// <remarks>
        /// <para>This information can be stored in the SIM/UICC or in the MT.</para>
        /// <para>If the command is supported by the phone but no number can be retrieved,
        /// an empty array is returned.</para>
        /// </remarks>
        public SubscriberInfo[] GetSubscriberNumbers()
        {
            return _theDevice.GetSubscriberNumbers();
        }

        /// <summary>
        /// Gets the phone's supported text mode character sets.
        /// </summary>
        /// <returns>A string array containing the names of the phone's supportet text mode character sets.</returns>
        /// <remarks>
        /// </remarks>
        public string[] GetSupportedCharacterSets()
        {
            return _theDevice.GetSupportedCharacterSets();
        }

        /// <summary>
        /// Gathers information that identifiy the connected device.
        /// </summary>
        /// <returns>An IdentificationInfo object containing data about the device.</returns>
        public IdentificationInfo IdentifyDevice()
        {
            LogIt("Identifying device...");
            var info = new IdentificationInfo
                           {
                               Manufacturer = _theDevice.RequestTAManufacturer(),
                               Model = _theDevice.RequestTAModel(),
                               Revision = _theDevice.RequestTARevision(),
                               SerialNumber = _theDevice.RequestTASerialNumber()
                           };
            LogIt("Manufacturer: " + info.Manufacturer);
            LogIt("Model: " + info.Model);
            LogIt("Revision: " + info.Revision);
            LogIt("Serial number: " + info.SerialNumber);
            return info;
        }

        /// <summary>
        /// Checks if it is required to acknowledge new directly routed incoming messages.
        /// </summary>
        /// <returns>true if directly routed incoming messages need to be acknowledged, false if not.</returns>
        public bool IsAcknowledgeRequired()
        {
            int num;
            int num2;
            int num3;
            int num4;
            _theDevice.GetCurrentMessageService(out num, out num2, out num3, out num4);
            return (num == 1);
        }

        /// <summary>
        /// Determines if there is actually a device connected that responds to commands.
        /// </summary>
        /// <returns>true if there is a device connected and responsive, otherwise false.</returns>
        /// <remarks>
        /// You can use this function after opening the port with Open to verify that there is really a device connected
        /// before processding.
        /// </remarks>
        public bool IsConnected()
        {
            return _theDevice.IsConnected();
        }

        /// <summary>
        /// Determines if the port is currently open.
        /// </summary>
        /// <returns>true if the port is open, otherwise false.</returns>
        /// <remarks><para>The port is open after a auccessful call to Open and must be closed with Close.</para>
        /// <para>This function does not check if there is actually a device connected, use the IsConnected function for that.</para>
        /// </remarks>
        public bool IsOpen()
        {
            return _theDevice.IsOpen();
        }

        /// <summary>
        /// Lists the network operators detected by the phone.
        /// </summary>
        /// <returns>An array of GsmCommunication.OperatorInfo2 objects containing the data of each operator.</returns>
        /// <remarks>If you want to determine the current operator, use the GsmCommMain.GetCurrentOperator method.</remarks>
        public OperatorInfo2[] ListOperators()
        {
            return _theDevice.ListOperators();
        }

        private void LogIt(string text)
        {
            if (LoglineAdded == null) return;
            text = DateTime.Now.ToString("HH:mm:ss.fff") + " " + text;
            LoglineAdded(this, text);
        }

        private void LogVersionInfo()
        {
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            Version version = executingAssembly.GetName().Version;
            string str = version.ToString(2);
            string str2 = version.ToString();
            string imageRuntimeVersion = executingAssembly.ImageRuntimeVersion;
            string text = string.Format("GSMComm {0} (Build {1} for .NET {2})", str, str2, imageRuntimeVersion);
            LogIt(text);
        }

        private void OnMessageSendComplete(OutgoingSmsPdu pdu)
        {
            if (MessageSendComplete == null) return;
            LogIt("Firing async MessageSendComplete event.");
            var e = new MessageEventArgs(pdu);
            MessageSendComplete.BeginInvoke(this, e, AsyncCallback, null);
        }

        private void OnMessageSendFailed(OutgoingSmsPdu pdu, Exception exception)
        {
            if (MessageSendFailed == null) return;
            LogIt("Firing async MessageSendFailed event.");
            var e = new MessageErrorEventArgs(pdu, exception);
            MessageSendFailed.BeginInvoke(this, e, AsyncCallback, null);
        }

        private void OnMessageSendStarting(OutgoingSmsPdu pdu)
        {
            if (MessageSendStarting == null) return;
            LogIt("Firing async MessageSendStarting event.");
            var e = new MessageEventArgs(pdu);
            MessageSendStarting.BeginInvoke(this, e, AsyncCallback, null);
        }

        /// <summary>
        /// Opens the connection to the device.
        /// </summary>
        /// <remarks>You can check the current connection state with the IsOpen method.
        /// </remarks>
        public void Open()
        {
            if (!VersionInfoLogged)
            {
                LogVersionInfo();
                VersionInfoLogged = true;
            }
            ConnectEvents();
            _theDevice.Open();
        }

        /// <summary>
        /// Reads a single short message.
        /// </summary>
        /// <param name="index">The index of the message to read.</param>
        /// <param name="storage">The storage to look in for the message.</param>
        /// <returns>A GsmCommunication.DecodedShortMessage object containing the message at the index specified.</returns>
        public DecodedShortMessage ReadMessage(int index, string storage)
        {
            LogIt("Reading message...");
            _theDevice.SelectReadStorage(storage);
            ShortMessageFromPhone message = _theDevice.ReadMessage(index);
            if (!Enum.IsDefined(typeof(PhoneMessageStatus), message.Status))
                throw new CommException("Unknown message status \"" + message.Status + "\"!");
            return new DecodedShortMessage(message.Index, DecodeShortMessage(message), (PhoneMessageStatus) Enum.Parse(typeof(PhoneMessageStatus), message.Status.ToString()), storage);
        }

        /// <summary>
        /// Reads and decodes short messages from phone.
        /// </summary>
        /// <param name="status">The status of the messages to read.</param>
        /// <param name="storage">The storage to look in for the messages.</param>
        /// <returns>An array of decoded messages.</returns>
        /// <remarks>As the decoded version of the message is not always guaranteed to
        /// be exactly the same when encoded back, do not use this function if you want
        /// to save the message in their original form. Use GsmCommMain.ReadRawMessages(GsmComm.GsmCommunication.PhoneMessageStatus,System.String) instead for that case.
        /// </remarks>
        public DecodedShortMessage[] ReadMessages(PhoneMessageStatus status, string storage)
        {
            LogIt("Reading messages...");
            var list = new ArrayList();
            _theDevice.SelectReadStorage(storage);
            foreach (ShortMessageFromPhone phone in _theDevice.ListMessages(status))
            {
                try
                {
                    if (!Enum.IsDefined(typeof(PhoneMessageStatus), phone.Status))
                        throw new CommException("Unknown message status \"" + phone.Status + "\"!");
                    var status2 = (PhoneMessageStatus) Enum.Parse(typeof(PhoneMessageStatus), phone.Status.ToString());
                    list.Add(new DecodedShortMessage(phone.Index, DecodeShortMessage(phone), status2, storage));
                }
                catch (Exception exception)
                {
                    LogIt("Error while decoding a message: " + exception.Message + " The message was ignored.");
                }
            }
            var array = new DecodedShortMessage[list.Count];
            list.CopyTo(array);
            return array;
        }

        /// <summary>
        /// Reads short messages in their original form from phone.
        /// </summary>
        /// <param name="status">The status of the messages to read.</param>
        /// <param name="storage">The storage to look in for the messages.</param>
        /// <returns>An array of undecoded short messages, as read from the phone.</returns>
        /// <remarks><para>This function is intended to download the messages exactly as they are
        /// returned from the phone, e.g. for a backup. If you want to use the messages
        /// directly, e.g. displaying them to the user without saving them, use the ReadMessages(GsmComm.GsmCommunication.PhoneMessageStatus,System.String) function instead.</para>
        /// <para>If you want to decode the saved messages later, you can use the DecodeShortMessage(GsmComm.GsmCommunication.ShortMessageFromPhone) function.</para>
        /// <para>You can import the saved message back to the phone using the WriteRawMessage(GsmComm.GsmCommunication.ShortMessageFromPhone,System.String) function.</para>
        /// </remarks>
        public ShortMessageFromPhone[] ReadRawMessages(PhoneMessageStatus status, string storage)
        {
            LogIt("Reading messages...");
            new ArrayList();
            _theDevice.SelectReadStorage(storage);
            return _theDevice.ListMessages(status);
        }

        /// <summary>
        /// Enables or disables the requirement to acknowledge new received short messages
        /// that are directly routed to the application.
        /// </summary>
        /// <param name="require">Set to true to require acknowledgements, set to false
        /// to turn off the requirement.</param>
        /// <remarks><para>It depends on the phone when this setting can actually be changed.
        /// Because of this, it is recommended to execute IsAcknowledgeRequired after a call to RequireAcknowledge to verify the new setting.</para>
        /// </remarks>
        public void RequireAcknowledge(bool require)
        {
            int num2;
            int num3;
            int num4;
            var service = require ? 1 : 0;
            _theDevice.SelectMessageService(service, out num2, out num3, out num4);
        }

        /// <summary>
        /// Resets all settings that are not stored in a profile to their factory defaults.
        /// </summary>
        /// <remarks>This function is useful if you don't know the state your phone is and
        /// want to set it up from scratch.</remarks>
        public void ResetToDefaultConfig()
        {
            _theDevice.ResetToDefaultConfig();
        }

        /// <summary>Selects the text mode character set.</summary>
        /// <param name="charset">The character set to use.</param>
        /// <remarks>
        /// <para>The GsmCommunication.Charset class contains some common character sets.</para>
        /// <para>To get a list of the character sets that the phone supports, use GetSupportedCharacterSets.</para>
        /// </remarks>
        public void SelectCharacterSet(string charset)
        {
            _theDevice.SelectCharacterSet(charset);
        }

        /// <summary>
        /// Sends a short message.
        /// </summary>
        /// <param name="pdu">The object containing the message to send.</param>
        /// <remarks>
        /// <para>This method sends the short message contained in the PDU
        /// object. If there is an error an exception will be thrown. Additionally,
        /// this function also fires the MessageSendFailed event upon an error and the MessageSendComplete event upon success.</para>
        /// <para>To send multiple messages in succession, use the SendMessages(GsmComm.PduConverter.OutgoingSmsPdu[]) function.</para>
        /// </remarks>
        public void SendMessage(OutgoingSmsPdu pdu)
        {
            SendMessage(pdu, true);
        }

        /// <summary>
        /// Sends a short message.
        /// </summary>
        /// <param name="pdu">The object containing the message to send.</param>
        /// <param name="throwExceptions">Indicates whether an exception should be
        /// thrown upon an error.</param>
        /// <remarks>
        /// <para>This method sends the short message contained in the PDU
        /// object. If there is an error an exception will be thrown. Additionally,
        /// this function also fires the MessageSendFailed event upon an error and the MessageSendComplete event upon success.</para>
        /// <para>Set the <b>throwExceptions</b> parameter to false if your message handling
        /// uses only the events fired by this method.</para>
        /// <para>To send multiple messages in succession, use the SendMessages(GsmComm.PduConverter.OutgoingSmsPdu[]) function.</para>
        /// </remarks>
        public void SendMessage(OutgoingSmsPdu pdu, bool throwExceptions)
        {
            LogIt("Sending message...");
            OnMessageSendStarting(pdu);
            byte num;
            try
            {
                num = _theDevice.SendMessage(pdu.ToString(), pdu.ActualLength);
            }
            catch (Exception exception)
            {
                LogIt("Error while sending the message: " + exception.Message);
                OnMessageSendFailed(pdu, exception);
                if (throwExceptions)
                    throw;
                return;
            }
            if (pdu.MessageReference == 0)
                pdu.MessageReference = num;
            LogIt("Message sent successfully.");
            OnMessageSendComplete(pdu);
        }

        /// <summary>
        /// Sends multiple messages in succession. Sending stops at the first error.
        /// </summary>
        /// <param name="pdus">The messages to send.</param>
        /// <remarks>
        /// </remarks>
        public void SendMessages(OutgoingSmsPdu[] pdus)
        {
            if (pdus == null)
            {
                LogIt("Failed. Message array is null.");
                throw new ArgumentNullException("pdus");
            }
            if (pdus.Length == 0)
                LogIt("Nothing to do!");
            else
            {
                LogIt(pdus.Length + " message(s) to send.");
                for (int i = 0; i < pdus.Length; i++)
                {
                    OutgoingSmsPdu pdu = pdus[i];
                    var strArray = new [] { "Sending message ", (i + 1).ToString(), " of ", pdus.Length.ToString(), "..." };
                    LogIt(string.Concat(strArray));
                    SendMessage(pdu);
                }
            }
        }

        /// <summary>
        /// Sets the new SMS service center address.
        /// </summary>
        /// <param name="data">An GsmCommunication.AddressData object containing the new address</param>
        /// <remarks>This command changes the SMSC address, through which SMS messages are transmitted.
        /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is
        /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals
        /// zero.</remarks>
        public void SetSmscAddress(AddressData data)
        {
            _theDevice.SetSmscAddress(data);
        }

        /// <summary>
        /// Sets the new SMS service center address.
        /// </summary>
        /// <param name="address">The new SMSC address</param>
        /// <remarks>This command changes the SMSC address, through which SMS messages are transmitted.
        /// In text mode, this setting is used by SMS sending and SMS writing commands. In PDU mode, this setting is
        /// used by the same commands, but only when the length of the SMSC address coded into the PDU data equals
        /// zero.</remarks>
        public void SetSmscAddress(string address)
        {
            _theDevice.SetSmscAddress(address);
        }

        private void TheDeviceLoglineAdded(object sender, string text)
        {
            if (LoglineAdded != null)
                LoglineAdded(this, text);
        }

        private void TheDeviceMessageReceived(object sender, MessageReceivedEventArgs e)
        {
            if (MessageReceived != null)
                MessageReceived(this, e);
        }

        private void TheDevicePhoneConnected(object sender, EventArgs e)
        {
            if (PhoneConnected != null)
                PhoneConnected(this, e);
        }

        private void TheDevicePhoneDisconnected(object sender, EventArgs e)
        {
            if (PhoneDisconnected != null)
                PhoneDisconnected(this, e);            
        }

        private void TheDeviceReceiveComplete(object sender, ProgressEventArgs e)
        {
            if (ReceiveComplete != null)
                ReceiveComplete(this, e);
        }

        private void TheDeviceReceiveProgress(object sender, ProgressEventArgs e)
        {
            if (ReceiveProgress != null)
                ReceiveProgress(this, e);
        }

        /// <summary>
        /// Stores a raw short message in the specified storage.
        /// </summary>
        /// <param name="message">The message to store.</param>
        /// <param name="storage">The storage to store the message in.</param>
        /// <returns>The index of the message. If the index could not be retrieved, zero is returned.</returns>
        /// <remarks>
        /// This function is useful for importing messages that were previously exported with ReadRawMessages(GsmComm.GsmCommunication.PhoneMessageStatus,System.String).
        /// </remarks>
        public int WriteRawMessage(ShortMessageFromPhone message, string storage)
        {
            _theDevice.SelectWriteStorage(storage);
            return _theDevice.WriteMessageToMemory(message.Data, message.Length, message.Status);
        }

        /// <summary>
        /// Stores a raw short message in the specified storage.
        /// </summary>
        /// <param name="message">The message to store.</param>
        /// <param name="storage">The storage to store the message in.</param>
        /// <param name="status">The status to set for the message.</param>
        /// <returns>The index of the message. If the index could not be retrieved, zero is returned.</returns>
        public int WriteRawMessage(ShortMessage message, string storage, int status)
        {
            _theDevice.SelectWriteStorage(storage);
            return _theDevice.WriteMessageToMemory(message.Data, message.Length, status);
        }

        /// <summary>
        /// Stores a raw short message in the specified storage without setting a specific message status.
        /// </summary>
        /// <param name="message">The message to store.</param>
        /// <param name="storage">The storage to store the message in.</param>
        /// <returns>The index of the message. If the index could not be retrieved, zero is returned.</returns>
        /// <remarks>
        /// <para>The message is stored with a predefined status set in the phone.</para>
        /// </remarks>
        public int WriteRawMessageWithoutStatus(ShortMessage message, string storage)
        {
            _theDevice.SelectWriteStorage(storage);
            return _theDevice.WriteMessageToMemory(message.Data, message.Length);
        }

        /// <summary>
        /// Gets the baud rate in use.
        /// </summary>
        public int BaudRate
        {
            get
            {
                return _theDevice.BaudRate;
            }
        }

        /// <summary>
        /// Gets or sets the delay in milliseconds between the checks to verify that the connection
        /// to the phone is still alive.
        /// </summary>
        public int ConnectionCheckDelay
        {
            get
            {
                return _theDevice.ConnectionCheckDelay;
            }
            set
            {
                _theDevice.ConnectionCheckDelay = value;
            }
        }

        /// <summary>
        /// Gets COM port currently connected to.
        /// </summary>
        public int PortNumber
        {
            get
            {
                return _theDevice.PortNumber;
            }
        }

        /// <summary>
        /// Gets the current communication timeout.
        /// </summary>
        public int Timeout
        {
            get
            {
                return _theDevice.Timeout;
            }
        }

        /// <summary>
        /// The method that handles the LoglineAdded event.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="text">The log text.</param>
        public delegate void LoglineAddedEventHandler(object sender, string text);

        /// <summary>
        /// The method that handles the MessageSendFailed event.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The GsmCommunication.MessageErrorEventArgs associated with the event.</param>
        public delegate void MessageErrorEventHandler(object sender, MessageErrorEventArgs e);

        /// <summary>
        /// The method that handles the MessageSendStarting and MessageSendComplete events.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The GsmCommunication.MessageEventArgs associated with the event.</param>
        public delegate void MessageEventHandler(object sender, MessageEventArgs e);
    }
}